import Env from './Env.js';
import axios from 'axios';
import CryptoJS from 'crypto-js'
export default class OkxRestService {
    /**
     * Singleton instance of OkxRestService
     * @type {OkxRestService}
     * @static
     */
    static instance = null;

    /**
     * Get the singleton instance of OkxRestService
     * @returns {OkxRestService}
     * @static
     */
    static getInstance() {
        if (OkxRestService.instance === null) {
            OkxRestService.instance = new OkxRestService();
        }
        return OkxRestService.instance;
    }

    /**
     * Create an instance of OkxRestService
     * @constructor
     * @private
     */
    constructor() {
        this.address = 'https://www.okx.com';
        this.simulationEnable = Env.OKX_SIMULATION_ENABLE;
        this.apiKey = Env.OKX_API_KEY;
        this.secretKey = Env.OKX_SECRET_KEY;
        this.passphrase = Env.OKX_PASSPHRASE;
    }

    /**
     * make a api call to okx
     * @param {*} name
     * @param {*} params
     */
    async call( method, name, params=null ) {
        let timestamp = new Date().toISOString();
        let requestPath = `/api/v5/${name}`;
        if ( 'GET' === method && null !== params ) {
            let queryString = new URLSearchParams(params).toString();
            requestPath += '?' + queryString;
        }
        let body = '';
        if ( 'POST' === method ) {
            body = JSON.stringify(params);
        }
        let sign = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(timestamp + method + requestPath + body, this.secretKey));

        const headers = {
            'Content-Type': 'application/json',
            'OK-ACCESS-KEY': this.apiKey,
            'OK-ACCESS-SIGN': sign,
            'OK-ACCESS-PASSPHRASE': this.passphrase,
            'OK-ACCESS-TIMESTAMP': timestamp,
        };
        if ( this.simulationEnable ) {
            headers['x-simulated-trading'] = '1';
        }

        try {
            this.log(method, requestPath, params);
            const response = await axios({
                method: method,
                url: `${this.address}${requestPath}`,
                headers: headers,
                data: body,
            });

            this.log('Response:', response);
            if (response.status === 200) {
                return response.data;
            } else {
                throw new Error(`Error: ${response.status} ${response.statusText}`);
            }
        } catch (error) {
            this.log('Error:', error);
            throw error;
        }
    }

    /**
     * 撮合交易/策略交易/策略委托下单/止盈止损卖出
     * @returns
     */
    async tradeOrderAlgoOcoSell( instId, quantity, priceMax, priceMin ) {
        let params = {};
        params.instId = instId;
        params.tdMode = 'cash';
        params.side = 'sell';
        params.ordType = 'oco';
        params.sz = quantity;
        params.tpTriggerPx = priceMax; // 触发止盈价格
        params.tpOrdPx = -1;
        params.slTriggerPx = priceMin; // 触发止损价格
        params.slOrdPx = -1;
        let result = await this.call('POST', 'trade/order-algo', params);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (POST:trade/order-algo) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data[0];
    }

    /**
     * 撮合交易/策略交易/获取策略委托单信息
     * @param {*} algoOrderId
     * @returns
     */
    async tradeOrderAlgoGet( algoOrderId ) {
        let params = {};
        params.algoId = algoOrderId;
        let result = await this.call('GET', 'trade/order-algo', params);
        if ( "51603" === result.code ) {
            return null; // 策略委托单不存在
        }
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (GET:trade/order-algo) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data[0];
    }

    /**
     * 撮合交易/策略交易/获取历史策略委托单信息
     * @param {*} algoId
     */
    async tradeOrderAlgoGetFromHistory( algoId ) {
        let params = {};
        params.ordType = 'oco';
        params.algoId = algoId;
        let result = await this.call('GET', 'trade/orders-algo-history', params);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (GET:trade/orders-algos-history) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data[0];
    }

    /**
     * 撮合交易/策略交易/撤销策略委托单
     * @param {*} instId
     * @param {*} algoOrderId
     * @returns
     */
    async tradeOrderAlgoCancel( instId, algoOrderId ) {
        let params = {};
        params.instId = instId;
        params.algoId = algoOrderId;
        let result = await this.call('POST', 'trade/cancel-algos', [params]);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (POST:trade/cancel-algos) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data[0];
    }

    /**
     * 撮合交易/下单 - 限价买入
     * @param {string} instId - Instrument ID
     * @param {number} price - Price
     * @param {number} amount - Amount
     * @returns {Promise<Object>}
     */
    async tradeOrderBuyLimit(instId, price, amount) {
        let params = {};
        params.instId = instId;
        params.tdMode = 'cash';
        params.side = 'buy';
        params.ordType = 'limit';
        params.sz = amount;
        params.px = price;
        let result = await this.call('POST', 'trade/order', params);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (POST:trade/order) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data[0];
    }

    /**
     * 撮合交易/订单查询
     * @param {*} instId - Instrument ID
     * @param {*} ordId - Order ID
     * @returns {Promise<Object>}
     */
    async tradeOrderGet(instId, ordId) {
        let params = {};
        params.instId = instId;
        params.ordId = ordId;
        let result = await this.call('GET', 'trade/order', params);
        return result.data[0];
    }

    /**
     * 撮合交易/订单撤销
     * @param {*} instId - Instrument ID
     * @param {*} ordId - Order ID
     * @returns {Promise<Object>}
     */
    async tradeOrderCancel(instId, ordId) {
        let params = {};
        params.instId = instId;
        params.ordId = ordId;
        let result = await this.call('POST', 'trade/cancel-order', params);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (POST:trade/cancel-order) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data[0];
    }

    /**
     * 获取交易产品基础信息
     * @returns {Promise<Object[]>}
     */
    async instrumentList() {
        let params = {};
        params.instType = 'SPOT';
        let result = await this.call('GET', 'public/instruments', params);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (GET:public/instruments) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data;
    }

    /**
     * 获取交易产品基础信息
     * @param {*} instId
     * @returns
     */
    async instrumentGet( instId ) {
        let params = {};
        params.instType = 'SPOT';
        params.instId = instId;
        let result = await this.call('GET', 'public/instruments', params);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (GET:public/instruments) : #${result.data[0].sCode} ${result.data[0].sMsg}`);
        }
        return result.data[0];
    }

    /**
     * 查看账户余额
     * @returns {Promise<Object>}
     */
    async accountBalanceGet(ccy) {
        let params = {};
        params.ccy = ccy;
        let result = await this.call('GET', 'account/balance', params);
        if ( result.code !== '0' ) {
            throw new Error(`OKX ERROR (GET:account/balance) : #${result.code} ${result.msg}`);
        }
        if ( 0 === result.data[0].details.length ) {
            return null;
        }
        return result.data[0].details[0];
    }

    /**
     * log messages to the console
     * @param  {...any} args
     * @private
     */
    log( ... args ) {
        console.log( '[OkxRestService]', ... args );
    }
}
